

#include "Core.hpp"
#include "Math.hpp"
#include "Color.hpp"
#include "SdlTracer.hpp"

#include <iostream>
#include <sstream>
#include <sys/time.h>
#include <cmath>

extern "C" 
{
	#include <lua.h>
	#include <lualib.h>
	#include <lauxlib.h>
}

using namespace com::toxiclabs::tsoc;
using namespace std;

lua_State * L;
Core * core;

int igetfield (const char *key)
 {
	int result;
	lua_pushstring(L, key);
	lua_gettable(L, -2);  /* get background[key] */
	if (!lua_isnumber(L, -1))
	cerr<<"invalid key"<<endl;
	result = (int)lua_tonumber(L, -1);
	lua_pop(L, 1);  /* remove number */
	return result;
}

/**
* pushes an integer into the table
*/
void isetfield(const char * name,int value)
{
	lua_pushstring(L,name);
	lua_pushinteger(L, value);
	lua_settable(L,-3);
}

void fsetfield(const char * name,float value)
{
	lua_pushstring(L,name);
	lua_pushnumber(L,value);
	lua_settable(L,-3);
}

/**
* Pushes a function into the table
*/
void fnsetfield(const char * name,lua_CFunction fn)
{
	lua_pushstring(L,name);
	lua_pushcfunction(L, fn);
	lua_settable(L,-3);
}

/*!
 * Load Wavefront
 */ 
int LoadMesh(lua_State * L)
{
	
	if(lua_gettop(L)>0)
	{
		const char * filename = lua_tostring(L,1);
		Obj3DLoader::Load(filename,core->scene);	
	}
		
	return 0;
}

int SetCamera(lua_State * L)
{
	if(lua_gettop(L)>3)
	{
			float angle=0.0f;
			
			Vector pos;
			Vector target;
			
			string name (lua_tostring(L,1));
			lua_rawgeti(L,2,1);
			lua_rawgeti(L,2,2);
			lua_rawgeti(L,2,3);
			pos.z=lua_tonumber(L,-1);
			pos.y=lua_tonumber(L,-2);
			pos.x=lua_tonumber(L,-3);
			pos.w=1.0f;
			
			
			lua_rawgeti(L,3,1);
			lua_rawgeti(L,3,2);
			lua_rawgeti(L,3,3);
			target.z=lua_tonumber(L,-1);
			target.y=lua_tonumber(L,-2);
			target.x=lua_tonumber(L,-3);
			target.w=1.0f;
			
			angle=lua_tonumber(L,4);
			
			Camera * camera = new Camera(name,pos,target,angle,0.0,10000.0f);
			
			core->scene->cameras.push_back(camera);
	}
	
	return 0;
}

int AddFilter(lua_State * L)
{
	if(lua_gettop(L)>0)
	{
		const char * name = lua_tostring(L,1);
	}
	return 0;
}

/*
* name
* material name
* light temperature (Kelvin)
* light intensity
* light emission angle ([0,90] degree)
*/
int SetLight(lua_State *  L)
{
	if(lua_gettop(L)>4)
	{
		const char * name = lua_tostring(L,1);
		const char * material_name = lua_tostring(L,2);
		float K = lua_tonumber(L,3);
		float power = lua_tonumber(L,4);
		float angle = lua_tonumber(L,5);
		
		Light * light = new Light();
		
		light->color=Color(K);
		light->power=power;
		light->angle=cos(DegToRad(angle));
		
		core->scene->lights.push_back(light);
		
		for(int n=0;n<core->scene->materials.size();n++)
		{
			if(core->scene->materials[n]->name==material_name)
			{
				cout<<"*Assign "<<name<< " to material "<<material_name<<endl;
				core->scene->materials[n]->light=light;
				core->scene->materials[n]->flags =  core->scene->materials[n]->flags | TSOC_MATERIAL_EMITTER;
				break;
			}
		}
	}
	
	return 0;
}

Core::Core(const char * filename)
{
	core = this;
	
	L = lua_open();
	luaL_openlibs(L);

	lua_newtable(L);
		isetfield("width",800);
		isetfield("height",600);
		isetfield("threads",2);
	lua_setglobal(L,"system");
	
	lua_newtable(L);
		isetfield("photons",16000);
		fsetfield("radius",0.05f);
		isetfield("bounces",0);
	lua_setglobal(L,"ppm");

	
	lua_register(L,"LoadMesh",LoadMesh);
	lua_register(L,"SetCamera",SetCamera);
	lua_register(L,"SetLight",SetLight);
	lua_register(L,"AddFilter",AddFilter);
	
	
	int status = luaL_loadfile(L, filename);
	lua_pcall(L,0,LUA_MULTRET,0);
	
	if(status!=0)std::cerr<<"Error loading script:"<<lua_tostring(L,-1)<<std::endl;
	else
	{
		
		scene = new Scene();
	}
}


Core::~Core()
{
	delete scene;
}

void Core::Run()
{
	Graph * graph;
	sdl::SDLTracer * raytracer;
	
	timeval t1,t2;
	long mtime;
	long sec,usec;
	
	
	
	/* setup scene */
	lua_getglobal(L,"setup");
	int status=lua_pcall(L, 0,  0, 0);
	if(status!=0)
	{
		std::cerr<<"Error loading setup() script:"<<lua_tostring(L,-1)<<std::endl;
		return;
	}
	
	lua_getglobal(L,"system");
	int width=igetfield("width");
	int height=igetfield("height");
	int threads=igetfield("threads");
	
	scene->ApplyCamera();
	
	graph = new Graph(&scene->triangles);
	
	/* render */
	
	raytracer = new sdl::SDLTracer(width,height,threads,scene,graph);
	
	raytracer->Run();
		
	
	
	delete graph;
	delete raytracer;
}
